<?php
/**
 * <pre>
 * <b>RUBIK IT Solution & Development ltd.</b>
 *
 * PROJECT      : rubik-base
 * COPYRIGHT    : 2008
 * ------------------------------------------------------
 * 
 * [GENERAL INFO OF THIS FILE] 
 *
 * </pre>
 * 
 * @version 		1.5
 * @author			hoadn <hoadn@rubik.com.vn>
 * @access			public
 * @package			rubik.base.dbo
 *
 * Created on	: May 7, 2008
 */

RCoreImporter::cls("helper.RStringHelper" );

class RubikDBFieldList {
	var $array = array ();
	var $_select = - 1;
	
	/**
	 * add RubikDBField into this list
	 *
	 * @param RubikDBField $rubikDBField
	 */
	function addDBField($rubikDBField) {
		$this->array [$rubikDBField->fieldName] = $rubikDBField;
	}
	
	/**
	 * Get DBField from this list
	 *
	 * @param string $fieldName
	 * @return RubikDBField
	 */
	function &getDBField($fieldName) {
		return $this->array [$fieldName];
	}
	
	/**
	 * Get the data array inside this
	 *
	 * @return array
	 */
	function getDBFieldList() {
		return $this->array;
	}
	
	/**
	 * set information from this then set the default value for the DBO
	 *
	 * @param AbstractRubikDBObject $abstractDB
	 */
	function setDefaultValueForDB(&$abstractDB) {
		foreach ( $this->array as $fieldName => $dbField ) {
			if ($dbField->dataType == SYM_TYPE_TIME_NUMBER && $dbField->defaultValue == SYM_CURRENT_TIME) {
				$abstractDB->$fieldName = time ();
			} else if ($dbField->isDate () && $dbField->defaultValue == SYM_CURRENT_TIME) {
				// TODO for now current date just support for MySQL or common SQL. 
				//	This should be done more generic in the future
				$abstractDB->$fieldName = date ( 'Y-m-d H:i:s' );
			} else {
				$abstractDB->$fieldName = $dbField->defaultValue;
			}
		}
	}
	
	/**
	 * Set all empty number fields to Zero number
	 * Using this in case of update some value that data is null
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 */
	function setEmptyNumberToZero(&$rubikDBO) {
		foreach ( $this->array as $fieldName => $dbField ) {
			if (RStringHelper::isEmpty($rubikDBO->$fieldName)) {
				$rubikDBO->$fieldName = 0;
			}
		}
	}
	
	/**
	 * Set null value for all dbFields
	 *
	 * @param AbstractRubikDBObject $abstractDB
	 */
	function setNullAllValues(&$abstractDB) {
		foreach ( $this->array as $fieldName => $dbField ) {
			$abstractDB->$fieldName = null;
		}
	}
	/**
	 * Set all empty field into null value
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 */
	function setEmptyToNull(&$abstractDB) {
		foreach ( $this->array as $fieldName => $dbField ) {
			if (RStringHelper::isEmpty ( $abstractDB->$fieldName )) {
				$abstractDB->$fieldName = null;
			}
		}
	}
	
	/**
	 * Preparing data before saving it into database.
	 * This step help do some generic and reduce problem of SQL injection
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 */
	function prepareDataForDB(&$rubikDBO) {
		foreach ( $this->array as $fieldName => $dbField ) {
			$val = $rubikDBO->$fieldName;
			$dbField->prepareDataForDB ( $rubikDBO );
		}
	}
	
	/**
	 * Preparing data before using it for view.
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 */
	function prepareDataForView(&$rubikDBO) {
		foreach ( $this->array as $fieldName => $dbField ) {
			$val = $rubikDBO->$fieldName;
			$dbField->prepareDataForView ( $rubikDBO );
		}
	}
	
	/**
	 * Get the select string from the list fields
	 * Ex: haveLast = true ==> "u.user_name, g.group_code, "
	 *     haveLast = false ==> "u.user_name, g.group_code"
	 * 
	 * @param boolean $haveLast true --> return have ", " at last
	 * @return string
	 */
	function getSelectString($dbInfo = null) {
		if ($this->_select === - 1) {
			$this->_select = "";
			foreach ( $this->array as $field ) {
				$this->_select .= $field->getNameWithAlias ( $dbInfo, true ) . ", ";
			}
		}
		return $this->_select;
	}
	
	/**
	 * Add more fields from other RubikDBFieldList
	 * Be carefull when using this function 
	 *
	 * @param RubikDBFieldList $rubikDBFieldList
	 */
	function addFields($rubikDBFieldList) {
		$this->array = array_merge ( $this->array, $rubikDBFieldList->array );
	}
	
	/**
	 * Preparing data before saving it into database.
	 * This step help do some generic and reduce problem of SQL injection
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 * @return true/array of error fields
	 */
	function checkMandatory($rubikDBO) {
		$arrError = array ();
		$hasError = false;
		foreach ( $this->array as $fieldName => $dbField ) {
			if (! $dbField->isKey && $dbField->isMandatory && (RStringHelper::isEmpty ( $rubikDBO->$fieldName ))) {
				$arrError [] = $fieldName;
				$hasError = true;
			}
		}
		if (! $hasError) {
			return true;
		}
		return $arrError;
	}
	
	/**
	 * Preparing data before saving it into database.
	 * This step help do some generic and reduce problem of SQL injection
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 * @param RubikDBInfo $dbInfo
	 * @return true/array of error fields
	 */
	function checkUnique($rubikDBO, $dbInfo) {
		$arrError = array ();
		$hasError = false;
		$keyField = $dbInfo->getKeyField ();
		
		$fieldName = $keyField->fieldName;
		$keyVal = $dbInfo->dbQuoteData ( $rubikDBO->$fieldName );
		if (empty ( $keyVal )) {
			$keyVal = "IS NOT NULL";
		} else {
			$keyVal = " <> $keyVal";
		}
		$keyWhere = " WHERE " . $keyField->getNameWithAlias ( $dbInfo ) . " $keyVal ";
		
		foreach ( $this->array as $fieldName => $dbField ) {
			if ($dbField->isUnique) {
				if ($this->checkExisting ( $fieldName, $rubikDBO, $dbInfo, $keyWhere )) {
					// exists the unique
					$arrError [] = sprintf ( SysLanguage::_ ( "{$fieldName}_Existing" ), $rubikDBO->$fieldName );
					$hasError = true;
				}
			}
		}
		if (! $hasError) {
			return true;
		}
		return $arrError;
	}
	
	/**
	 * Check if the data inside $fieldName of $rubikDBO is existing or not.
	 *
	 * @param string/array $fieldOrArrayFields
	 * @param AbstractRubikDBObject $rubikDBO
	 * @param RubikDBInfo $dbInfo if empty the system will automatically calculate this
	 * @param string $keyWhere if empty the system will automatically calculate this
	 * @param boolean $fillBack should fill back data if exist?
	 * @return boolean
	 */
	function checkExisting($fieldOrArrayFields, $rubikDBO, $dbInfo = null, $keyWhere = null, $fillBack = false) {
		if (empty ( $dbInfo )) {
			$dbInfo = RubikDBFactory::getDBInfo ( $rubikDBO );
		}
		
		$where = $this->createWhereCheckExist ( $fieldOrArrayFields, $rubikDBO, $dbInfo, $keyWhere );
		
		if ($fillBack) {
			$sql = $dbInfo->getSelectSQL () . $where;
			$db = $dbInfo->getDatabase ();
			$db->setQuery ( $sql );
			$obj = $db->loadObject ();
			if (! empty ( $obj )) {
				$rubikDBO->bind ( $obj );
				return true;
			}
			return false;
		} else {
			$sql = $dbInfo->getSelectCountSQL ( $where );
			$db = $dbInfo->getDatabase ();
			$db->setQuery ( $sql );
			$iRsD = $db->loadResult ();
			return ($iRsD > 0);
		}
	}
	
	/**
	 * Create the where to check Existing.
	 *
	 * @param string/array $fieldOrArrayFields
	 * @param AbstractRubikDBObject $rubikDBO
	 * @param RubikDBInfo $dbInfo if empty the system will automatically calculate this
	 * @param string $keyWhere if empty the system will automatically calculate this
	 * @return string
	 */
	function createWhereCheckExist($fieldOrArrayFields, $rubikDBO, $dbInfo, $keyWhere = null) {
		if (! is_array ( $fieldOrArrayFields )) {
			$fieldOrArrayFields = array ($fieldOrArrayFields );
		}
		
		$keyField = $dbInfo->getKeyField ();
		$keyName = $keyField->fieldName;
		if (! in_array ( $keyName, $fieldOrArrayFields )) {
			if (empty ( $keyWhere )) {
				$keyVal = $dbInfo->dbQuoteData ( $rubikDBO->$keyName );
				if (empty ( $keyVal )) {
					$keyVal = "IS NOT NULL";
				} else {
					$keyVal = " <> $keyVal";
				}
				$keyWhere = " WHERE " . $keyField->getNameWithAlias ( $dbInfo ) . " $keyVal ";
			}
		} else {
			$keyWhere = "WHERE 1=1";
		}
		
		$where[]		= $keyWhere;
		foreach ($fieldOrArrayFields as $fieldName) {
			$val 		= $dbInfo->dbQuoteData($rubikDBO->$fieldName);
			$quoteName 	= $dbInfo->dbQuoteField($fieldName);
			if ($val === null) {
				$where[]	= "$quoteName IS NULL";
			} else if (empty($val)) {
				$where[]	= "$quoteName IS NULL OR $quoteName=''";
			}
			else {
				$where[]	= "$quoteName=$val";
			}
		}
		return implode ( " AND ", $where );
	}
	
	/**
	 * Create the update SQL for input RubikDB
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 * @param boolean $ignoreNull update with null values or not
	 * @return SQL string
	 */
	function createUpdateSQL(&$rubikDBO, $ignoreNull = true) {
		$dbInfo = RubikDBFactory::getDBInfo ( $rubikDBO );
		$fieldList = $this->getFieldList ( $rubikDBO, $ignoreNull );
		$size = count ( $fieldList );
		$result = "";
		for($i = 0; $i < $size; $i ++) {
			$field = &$fieldList [$i];
			$val = $rubikDBO->$field;
			$field = $dbInfo->dbQuoteField ( $field );
			if ($val === null) {
				$result .= $field . " = NULL, ";
			} else {
				$result .= $field . " = " . $dbInfo->dbQuoteData ( $val ) . ", ";
			}
		}
		if (empty ( $result )) {
			return false;
		}
		$result = substr ( $result, 0, strlen ( $result ) - 2 );
		return $result;
	}
	
	/**
	 * Get the field name list
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 * @param boolean $ignoreNull update with null values or not
	 * @param boolean $returnDbField true return value is RubikDbField else return string
	 * @return array()
	 */
	function getFieldList($rubikDBO, $ignoreNull = true, $returnDbField = false) {
		$fieldList = array ();
		
		foreach ( $this->array as $fieldName => $dbField ) {
			if ($ignoreNull && ($rubikDBO->$fieldName) === null) {
				continue;
			}
			if ($returnDbField) {
				$fieldList [] = $dbField;
			} else {
				$fieldList [] = $fieldName;
			}
		}
		return $fieldList;
	}
	
	/**
	 * Remove the slash when php enable mode: magic_quotes_gpc .
	 * Using this function before set data into view.
	 * 
	 * The idea of this: When a form submit then bind value into a DBO (magic_quotes_gpc = on)
	 * if you want to fill data back from current DBO into form --> you must use this function 
	 * for smart strip the slash.
	 * 
	 * Why smart?
	 * 	This function just strip the slash if (magic_quotes_gpc == on)
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 * @return boolean strip or not?
	 */
	function smartStripSlash(&$rubikDBO) {
		if (! get_magic_quotes_gpc ()) {
			return false;
		}
		foreach ( $this->array as $fieldName => $dbField ) {
			$rubikDBO->$fieldName = stripcslashes ( $rubikDBO->$fieldName );
		}
		return true;
	}
	
	/**
	 * Remove the slash when php enable mode: magic_quotes_gpc .
	 * Using this function before set data into view.
	 * 
	 * The idea of this: When a form submit then bind value into a DBO (magic_quotes_gpc = on)
	 * if you want to fill data back from current DBO into form --> you must use this function 
	 * for smart strip the slash.
	 * 
	 * Why smart?
	 * 	This function just strip the slash if (magic_quotes_gpc == on)
	 *
	 * @param AbstractRubikDBObject $rubikDBO
	 * @return boolean strip or not?
	 */
	function smartAddSlashes(&$rubikDBO) {
		if (! get_magic_quotes_gpc ()) {
			return false;
		}
		foreach ( $this->array as $fieldName => $dbField ) {
			$rubikDBO->$fieldName = addslashes ( $rubikDBO->$fieldName );
		}
		return true;
	}
	
	/**
	 * Trim the string of public properties in DTO
	 *
	 * @param AbstractRubikDBObject $dbo
	 * @param string $charlist
	 * 
	 * @return AbstractRubikDBObject
	 */
	function trimString(&$rubikDBO, $charlist = null) {
		foreach ( $this->array as $fieldName => $dbField ) {
			$rubikDBO->$fieldName = trim ( $rubikDBO->$fieldName, $charlist );
		}
		return $rubikDBO;
	}
}
?>
